import os
import sys
import ctypes
import subprocess
import winreg
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
from PyQt5.QtGui import QIcon, QPalette, QColor, QFont
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QFileDialog, QMessageBox,
    QComboBox, QFormLayout, QTextEdit, QListWidget, QListWidgetItem
)

# ---------- 工具 ----------
def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except:
        return False

def elevate():
    ctypes.windll.shell32.ShellExecuteW(
        None, "runas", sys.executable, f'"{__file__}"', None, 1)
    sys.exit()

def win_theme_is_dark():
    try:
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                            r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") as key:
            return winreg.QueryValueEx(key, "AppsUseLightTheme")[0] == 0
    except:
        return False

def get_service_config(service_name: str) -> dict:
    """读取服务配置"""
    key_path = rf"SYSTEM\CurrentControlSet\Services\{service_name}"
    try:
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
            def _get(name, default=""):
                try:
                    return winreg.QueryValueEx(key, name)[0]
                except FileNotFoundError:
                    return default
            return {
                "binpath": _get("ImagePath"),
                "startdir": _get("AppDirectory"),
                "params": _get("AppParameters"),
                "desc": _get("Description"),
                "start": _get("Start")
            }
    except Exception:
        return {}

class NSSMWorker(QThread):
    log = pyqtSignal(str)
    finished = pyqtSignal(bool, str)

    def __init__(self, commands):
        super().__init__()
        self.commands = commands

    def run(self):
        try:
            for cmd in self.commands:
                self.log.emit(f">>> {cmd}")
                proc = subprocess.run(
                    cmd, shell=True, capture_output=True, text=True,
                    creationflags=subprocess.CREATE_NO_WINDOW
                )
                if proc.stdout:
                    self.log.emit(proc.stdout.strip())
                if proc.stderr:
                    self.log.emit(proc.stderr.strip())
                if proc.returncode != 0:
                    self.finished.emit(False, f"命令失败：{cmd}\n{proc.stderr}")
                    return
            self.finished.emit(True, "全部完成")
        except Exception as e:
            self.finished.emit(False, str(e))

class NSSMInstallerGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("NSSM Service Installer")
        self.setGeometry(100, 100, 900, 600)
        self.setWindowIcon(QIcon("Heart.ico"))

        if not is_admin():
            elevate()

        self.settings = QSettings("NSSMGui", "MainWindow")
        self.restoreGeometry(self.settings.value("geometry", b""))
        self.init_ui()
        self.apply_theme()
        self.load_services()
        self.load_settings()

    def init_ui(self):
        central = QWidget()
        self.setCentralWidget(central)
        lay = QHBoxLayout(central)

        # 左侧服务列表
        self.service_list = QListWidget()
        self.service_list.itemDoubleClicked.connect(self.fill_from_service)
        left = QWidget()
        left_lay = QVBoxLayout(left)
        left_lay.addWidget(QLabel("现有服务（双击填充）"))
        left_lay.addWidget(self.service_list)
        lay.addWidget(left, 1)

        # 右侧表单
        right = QWidget()
        right_lay = QVBoxLayout(right)

        form = QFormLayout()
        right_lay.addLayout(form)

        red_palette = QPalette()
        red_palette.setColor(QPalette.Text, Qt.red)

        self.app_path_entry = QLineEdit()
        self.app_path_entry.setPalette(red_palette)
        btn_app = QPushButton("浏览")
        btn_app.clicked.connect(self.browse_app)
        h1 = QHBoxLayout()
        h1.addWidget(self.app_path_entry)
        h1.addWidget(btn_app)
        form.addRow("程序路径*：", h1)

        self.startup_dir_entry = QLineEdit()
        btn_dir = QPushButton("浏览")
        btn_dir.clicked.connect(self.browse_dir)
        h2 = QHBoxLayout()
        h2.addWidget(self.startup_dir_entry)
        h2.addWidget(btn_dir)
        form.addRow("启动目录：", h2)

        self.arguments_entry = QLineEdit()
        form.addRow("参数：", self.arguments_entry)

        self.display_name_entry = QLineEdit()
        self.display_name_entry.setPalette(red_palette)
        form.addRow("显示名称*：", self.display_name_entry)

        self.description_entry = QLineEdit()
        form.addRow("描述：", self.description_entry)

        self.start_type_combo = QComboBox()
        self.start_type_combo.addItems(
            ["SERVICE_AUTO_START", "SERVICE_DEMAND_START", "SERVICE_DISABLED"])
        form.addRow("启动方式*：", self.start_type_combo)

        btn_lay = QHBoxLayout()
        self.install_btn = QPushButton("安装")
        self.install_btn.clicked.connect(self.install_service)
        self.remove_btn = QPushButton("删除")
        self.remove_btn.clicked.connect(self.remove_service)
        btn_lay.addWidget(self.install_btn)
        btn_lay.addWidget(self.remove_btn)
        right_lay.addLayout(btn_lay)

        self.log_edit = QTextEdit()
        self.log_edit.setFont(QFont("Consolas", 9))
        self.log_edit.setMaximumHeight(150)
        right_lay.addWidget(QLabel("日志："))
        right_lay.addWidget(self.log_edit)

        lay.addWidget(right, 2)
        self.setAcceptDrops(True)

    def apply_theme(self):
        if win_theme_is_dark():
            self.setStyleSheet("""
                QMainWindow,QWidget{background:#202020;color:#ffffff}
                QTextEdit{background:#121212;border:1px solid #444}
                QPushButton{background:#0a84ff;color:#fff;border:none;padding:4px 8px}
                QPushButton:hover{background:#1177dd}
                QLineEdit,QComboBox{background:#2c2c2c;border:1px solid #555;color:#fff}
            """)
        else:
            self.setStyleSheet("")

    def browse_app(self):
        path, _ = QFileDialog.getOpenFileName(self, "选择 exe", "", "可执行文件 (*.exe)")
        if path:
            self.app_path_entry.setText(path)
            self.startup_dir_entry.setText(os.path.dirname(path))

    def browse_dir(self):
        path = QFileDialog.getExistingDirectory(self, "选择目录")
        if path:
            self.startup_dir_entry.setText(path)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            path = url.toLocalFile()
            if os.path.isfile(path) and path.lower().endswith(".exe"):
                self.app_path_entry.setText(path)
                self.startup_dir_entry.setText(os.path.dirname(path))
            elif os.path.isdir(path):
                self.startup_dir_entry.setText(path)

    def load_services(self):
        self.service_list.clear()
        try:
            data = subprocess.check_output(
                ["sc", "query", "type=", "service", "state=", "all"],
                creationflags=subprocess.CREATE_NO_WINDOW
            ).decode(errors="ignore")
            for line in data.splitlines():
                if "SERVICE_NAME:" in line:
                    name = line.split(":", 1)[1].strip()
                    QListWidgetItem(name, self.service_list)
        except Exception as e:
            self.log_edit.append("读取服务列表失败：" + str(e))

    def fill_from_service(self, item):
        service_name = item.text()
        cfg = get_service_config(service_name)

        cmdline = cfg.get("binpath", "")
        exe_path = ""
        args = ""
        if cmdline.startswith('"'):
            parts = cmdline.split('"', 2)
            if len(parts) >= 3:
                exe_path = parts[1]
                args = parts[2].strip()
        else:
            parts = cmdline.split(" ", 1)
            exe_path = parts[0]
            args = parts[1] if len(parts) > 1 else ""

        self.display_name_entry.setText(service_name)
        self.app_path_entry.setText(exe_path)
        self.startup_dir_entry.setText(cfg.get("startdir", os.path.dirname(exe_path) if exe_path else ""))
        self.arguments_entry.setText(args)
        self.description_entry.setText(cfg.get("desc", ""))

        # 启动方式
        start_map = {2: "SERVICE_AUTO_START",
                     3: "SERVICE_DEMAND_START",
                     4: "SERVICE_DISABLED"}
        st = start_map.get(cfg.get("start"), "SERVICE_AUTO_START")
        idx = self.start_type_combo.findText(st)
        if idx >= 0:
            self.start_type_combo.setCurrentIndex(idx)

    def install_service(self):
        app = self.app_path_entry.text().strip()
        disp = self.display_name_entry.text().strip()
        if not app or not disp:
            QMessageBox.warning(self, "提示", "带 * 的字段必须填写！")
            return
        service = disp.replace(" ", "_")
        commands = [
            f'nssm install {service} "{app}"',
            f'nssm set {service} AppDirectory "{self.startup_dir_entry.text()}"',
            f'nssm set {service} AppParameters "{self.arguments_entry.text()}"',
            f'nssm set {service} DisplayName "{disp}"',
            f'nssm set {service} Description "{self.description_entry.text()}"',
            f'nssm set {service} Start {self.start_type_combo.currentText()}'
        ]
        self.run_commands(commands, "安装")

    def remove_service(self):
        disp = self.display_name_entry.text().strip()
        if not disp:
            QMessageBox.warning(self, "提示", "请先填写显示名称！")
            return
        service = disp.replace(" ", "_")
        commands = [f'nssm remove {service} confirm']
        self.run_commands(commands, "删除")

    def run_commands(self, commands, action):
        self.log_edit.clear()
        self.worker = NSSMWorker(commands)
        self.worker.log.connect(self.log_edit.append)
        self.worker.finished.connect(lambda ok, msg: self.on_finished(ok, msg, action))
        self.install_btn.setEnabled(False)
        self.remove_btn.setEnabled(False)
        self.worker.start()

    def on_finished(self, ok, msg, action):
        self.install_btn.setEnabled(True)
        self.remove_btn.setEnabled(True)
        self.load_services()
        if ok:
            QMessageBox.information(self, "完成", f"{action}成功！")
        else:
            QMessageBox.critical(self, "错误", msg)

    def load_settings(self):
        self.app_path_entry.setText(self.settings.value("app_path", ""))
        self.startup_dir_entry.setText(self.settings.value("startup_dir", ""))
        self.arguments_entry.setText(self.settings.value("args", ""))
        self.display_name_entry.setText(self.settings.value("display_name", ""))
        self.description_entry.setText(self.settings.value("desc", ""))
        st = self.settings.value("start_type", "SERVICE_AUTO_START")
        idx = self.start_type_combo.findText(st)
        if idx >= 0:
            self.start_type_combo.setCurrentIndex(idx)

    def save_settings(self):
        self.settings.setValue("geometry", self.saveGeometry())
        self.settings.setValue("app_path", self.app_path_entry.text())
        self.settings.setValue("startup_dir", self.startup_dir_entry.text())
        self.settings.setValue("args", self.arguments_entry.text())
        self.settings.setValue("display_name", self.display_name_entry.text())
        self.settings.setValue("desc", self.description_entry.text())
        self.settings.setValue("start_type", self.start_type_combo.currentText())

    def closeEvent(self, e):
        self.save_settings()
        super().closeEvent(e)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    w = NSSMInstallerGUI()
    w.show()
    sys.exit(app.exec_())
